package com.dp;

import com.dp.annotation.MyAutowired;
import com.dp.annotation.MyComponent;
import com.dp.exception.NotFountBeanException;
import com.dp.util.IocUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author:defuzhu
 * 模拟 ApplicationContext 上线文类
 */
public class MyApplicationContext {

    //存储实例后的Bean容器
    private Map<String, Object> beanMap = new ConcurrentHashMap<String, Object>();
    //需要实例化的对象Class全路径名
    private Set<String> classNameSet = new HashSet<String>();

    public MyApplicationContext() {
        try {
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化 IOC 容器
     * @throws Exception
     */
    public void init() throws Exception {

        /**
         * step1.定位资源，即要扫描的目录
         */
        String beanScanPath = getBeanScanPath("ioc.bean.scan");

        /**
         * step2. 加载需要实例化的 class 全路径名
         */
        loadBeanClass(beanScanPath);

        /**
         * step3. 实例化 classNameSet 中的类
         */
        registerBean();

        dependenceInjection();

    }

    /**
     * 根据Bean名称获取对象实例
     *
     * @param name
     * @return
     */
    public Object getBean(String name) throws NotFountBeanException {
        return beanMap.get(name);
    }


    /**
     * step1 读取 application.properties 文件的ioc.bean.scan属性，得到需要扫描的文件夹
     * 定位需要进行实例化JAVA类
     */
    private String getBeanScanPath(String key) {
        Properties properties = IocUtil.getPropertyByName("application.properties");
        return properties.get(key).toString();

    }

    /**
     * 根据指定的包名，加载其下面所有以 .clas 结尾的类文件名，保存到classNameSet 集合中
     * @param packageName
     */
    public void loadBeanClass(String packageName) {
        //com.cn.dp 转换成 com/cn/dp文件夹路径
        String filePath = packageName.replace(".", "/");
        URL url = this.getClass().getClassLoader().getResource(filePath);
        //得到根文件夹
        File rootFile = new File(url.getFile());
        //遍历所有文件夹
        if (rootFile != null) {
            for (File file : rootFile.listFiles()) {
                if (file.isDirectory()) {
                    //如果是文件夹则递归继续往下扫描
                    loadBeanClass(packageName + "." + file.getName());
                } else {
                    if (file.getName().indexOf(".class") > 0) {
                        //保存class类名
                        classNameSet.add(packageName + "." + file.getName().replace(".class", ""));
                    }
                }
            }
        }
    }

    /**
     * 实例化 classNameSet 中的类对象，保存到 beanMap 容器中
     * @throws Exception
     */
    private void registerBean() throws Exception {
        if (CollectionUtils.isNotEmpty(classNameSet)) {
            for (String className : classNameSet) {
                //实例化对象放入beanMap
                Class clazz = Class.forName(className);

                MyComponent myComponent = (MyComponent) clazz.getAnnotation(MyComponent.class);
                if (myComponent == null) continue;

                /**
                 * 定义bean key名称，默认去 类的简称（不含包名）
                 * 如果MyComponent注解的value
                 */

                String beanName = (StringUtils.isEmpty(myComponent.value())) ? IocUtil.toLowercaseIndex(clazz.getSimpleName()) : myComponent.value();
                beanMap.put(beanName, clazz.newInstance());
            }
        }
    }

    /**
     * 对已经实例化的对象，检查其内部要依赖注入的属性，进行属性注入
     * @param o
     */
    private void doInjection(Object o) throws Exception {

        //获取类的属性，进行依赖注入
        Field[] fields = o.getClass().getDeclaredFields();
        if (ArrayUtils.isNotEmpty(fields)) {
            for (Field file : fields) {
                MyAutowired autowired = file.getAnnotation(MyAutowired.class);
                if (autowired != null) {
                    //得到beanName 验证该Bean是否已经实例化了
                    String beanName = (StringUtils.isEmpty(autowired.value()))? IocUtil.toLowercaseIndex(file.getType().getSimpleName()):autowired.value();
                    //如果bean已经被实例化了,否则创建对象
                    if (!beanMap.containsKey(beanName)) {
                        Class clazz = file.getType();
                        beanMap.put(beanName, clazz.newInstance());
                    }
                    //调用对象set方法注入属性
                    file.setAccessible(true);
                    file.set(o, beanMap.get(beanName));
                    //递归当前实例化的对象的属性注入
                    doInjection(beanMap.get(beanName));
                }

            }
        }
    }

    /**
     * 依赖注入
     */
    private void dependenceInjection() throws Exception {
        if (MapUtils.isEmpty(beanMap)) return;
        for (Object o : beanMap.values()) {
            doInjection(o);
        }
    }
}
